package org.hxuhao.summer.io;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Function;

/**
 * 资源解析器
 */
public class ResourceResolver {

    Logger logger = LoggerFactory.getLogger(ResourceResolver.class);

    String basePackage;

    public ResourceResolver(String basePackage) {
        this.basePackage = basePackage;
    }

    /**
     * 扫描包下的所有文件，并执行mapper逻辑，返回资源列表。
     * 此函数仅负责扫描所有文件，具体业务逻辑由上层传入，方便在各种解析逻辑中复用。
     *
     * @param mapper 根据文件生成资源的具体逻辑函数，由于本方法会扫描所有文件，一些过滤逻辑也需要放在mapper中实现。
     * @param <R>    资源类型
     * @return 资源列表
     */
    public <R> List<R> scan(Function<Resource, R> mapper) {
        String basePackagePath = basePackage.replace(".", "/");
        String path = basePackagePath; // 这里没弄明白为啥要多开一个变量
        try {
            List<R> collector = new ArrayList<>();
            scan0(basePackagePath, path, collector, mapper);
            return collector;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    <R> void scan0(String basePackagePath, String path, List<R> collector, Function<Resource, R> mapper) throws IOException, URISyntaxException {
        logger.atDebug().log("scan path: {}", path);
        Enumeration<URL> urls = getContextClassLoader().getResources(path); // 通过classLoader加载资源
        HashSet<String> uriBaseStrSet = new HashSet<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            URI uri = url.toURI();

            String uriStr = removeTrailingSlash(uriToString(uri));
            logger.atDebug().log("scan uriStr: {}", uriStr);
            String uriBaseStr = uriStr.substring(0, uriStr.length() - basePackagePath.length());
            if (uriBaseStr.startsWith("file:")) {
                uriBaseStr = uriBaseStr.substring(5);
            }
            if (uriBaseStrSet.contains(uriBaseStr)) {
                logger.warn("already scan {}", uriBaseStr); // TOFIX: 这里有重复扫描的问题，临时使用Set来解决
                continue;
            } else {
                uriBaseStrSet.add(uriBaseStr);
            }
            if (uriStr.startsWith("jar:")) {
                scanFile(true, uriBaseStr, jarUriToPath(basePackagePath, uri), collector, mapper);
            } else {
                scanFile(false, uriBaseStr, Paths.get(uri), collector, mapper);
            }
        }
    }

    <R> void scanFile(boolean isJar, String base, Path root, List<R> collector, Function<Resource, R> mapper) throws IOException {
        String baseDir = removeTrailingSlash(base);
        logger.atDebug().log("baseDir: {} root: {}", base, root);
        Files.walk(root).filter(Files::isRegularFile).forEach(file -> {
            Resource res = null;
            if (isJar) {
                res = new Resource(baseDir, removeLeadingSlash(file.toString()));
            } else {
                String path = file.toString();
                String name = removeLeadingSlash(path.substring(baseDir.length()));
                res = new Resource("file:" + path, name);
            }
            logger.atDebug().log("found resource: {}", res);
            R r = mapper.apply(res);
            if (r != null) {
                collector.add(r);
            }
        });
    }

    ClassLoader getContextClassLoader() {
        ClassLoader cl = null;
        cl = Thread.currentThread().getContextClassLoader();
        if (cl == null) {
            cl = getClass().getClassLoader();
        }
        return cl;
    }

    String uriToString(URI uri) {
        return URLDecoder.decode(uri.toString(), StandardCharsets.UTF_8);
    }

    String removeLeadingSlash(String s) {
        if (s.startsWith("/") || s.startsWith("\\")) {
            s = s.substring(1);
        }
        return s;
    }

    String removeTrailingSlash(String s) {
        if (s.endsWith("/") || s.endsWith("\\")) {
            s = s.substring(0, s.length() - 1);
        }
        return s;
    }

    Path jarUriToPath(String basePackagePath, URI jarUri) throws IOException {
        return FileSystems.newFileSystem(jarUri, Map.of()).getPath(basePackagePath);
    }
}
